// Tencent is pleased to support the open source community by making RapidJSON
// available.
//
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All
// rights reserved.
//
// Licensed under the MIT License (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of the License
// at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#ifndef RAPIDJSON_PRETTYWRITER_H_
#define RAPIDJSON_PRETTYWRITER_H_

#include "writer.h"

#ifdef __GNUC__
RAPIDJSON_DIAG_PUSH
RAPIDJSON_DIAG_OFF(effc++)
#endif

#if defined(__clang__)
RAPIDJSON_DIAG_PUSH
RAPIDJSON_DIAG_OFF(c++ 98 - compat)
#endif

RAPIDJSON_NAMESPACE_BEGIN

//! Combination of PrettyWriter format flags.
/*! \see PrettyWriter::SetFormatOptions
 */
enum PrettyFormatOptions {
  kFormatDefault = 0,         //!< Default pretty formatting.
  kFormatSingleLineArray = 1  //!< Format arrays on a single line.
};

//! Writer with indentation and spacing.
/*!
    \tparam OutputStream Type of output os.
    \tparam SourceEncoding Encoding of source string.
    \tparam TargetEncoding Encoding of output stream.
    \tparam StackAllocator Type of allocator for allocating memory of stack.
*/
template <typename OutputStream, typename SourceEncoding = UTF8<>,
          typename TargetEncoding = UTF8<>,
          typename StackAllocator = CrtAllocator,
          unsigned writeFlags = kWriteDefaultFlags>
class PrettyWriter : public Writer<OutputStream, SourceEncoding, TargetEncoding,
                                   StackAllocator, writeFlags> {
 public:
  typedef Writer<OutputStream, SourceEncoding, TargetEncoding, StackAllocator,
                 writeFlags>
      Base;
  typedef typename Base::Ch Ch;

  //! Constructor
  /*! \param os Output stream.
      \param allocator User supplied allocator. If it is null, it will create a
     private one. \param levelDepth Initial capacity of stack.
  */
  explicit PrettyWriter(OutputStream &os, StackAllocator *allocator = 0,
                        size_t levelDepth = Base::kDefaultLevelDepth)
      : Base(os, allocator, levelDepth),
        indentChar_(' '),
        indentCharCount_(4),
        formatOptions_(kFormatDefault) {}

  explicit PrettyWriter(StackAllocator *allocator = 0,
                        size_t levelDepth = Base::kDefaultLevelDepth)
      : Base(allocator, levelDepth), indentChar_(' '), indentCharCount_(4) {}

#if RAPIDJSON_HAS_CXX11_RVALUE_REFS
  PrettyWriter(PrettyWriter &&rhs)
      : Base(std::forward<PrettyWriter>(rhs)),
        indentChar_(rhs.indentChar_),
        indentCharCount_(rhs.indentCharCount_),
        formatOptions_(rhs.formatOptions_) {}
#endif

  //! Set custom indentation.
  /*! \param indentChar       Character for indentation. Must be whitespace
     character (' ', '\\t', '\\n', '\\r'). \param indentCharCount  Number of
     indent characters for each indentation level. \note The default indentation
     is 4 spaces.
  */
  PrettyWriter &SetIndent(Ch indentChar, unsigned indentCharCount) {
    RAPIDJSON_ASSERT(indentChar == ' ' || indentChar == '\t' ||
                     indentChar == '\n' || indentChar == '\r');
    indentChar_ = indentChar;
    indentCharCount_ = indentCharCount;
    return *this;
  }

  //! Set pretty writer formatting options.
  /*! \param options Formatting options.
   */
  PrettyWriter &SetFormatOptions(PrettyFormatOptions options) {
    formatOptions_ = options;
    return *this;
  }

  /*! @name Implementation of Handler
      \see Handler
  */
  //@{

  bool Null() {
    PrettyPrefix(kNullType);
    return Base::EndValue(Base::WriteNull());
  }
  bool Bool(bool b) {
    PrettyPrefix(b ? kTrueType : kFalseType);
    return Base::EndValue(Base::WriteBool(b));
  }
  bool Int(int i) {
    PrettyPrefix(kNumberType);
    return Base::EndValue(Base::WriteInt(i));
  }
  bool Uint(unsigned u) {
    PrettyPrefix(kNumberType);
    return Base::EndValue(Base::WriteUint(u));
  }
  bool Int64(int64_t i64) {
    PrettyPrefix(kNumberType);
    return Base::EndValue(Base::WriteInt64(i64));
  }
  bool Uint64(uint64_t u64) {
    PrettyPrefix(kNumberType);
    return Base::EndValue(Base::WriteUint64(u64));
  }
  bool Double(double d) {
    PrettyPrefix(kNumberType);
    return Base::EndValue(Base::WriteDouble(d));
  }

  bool RawNumber(const Ch *str, SizeType length, bool copy = false) {
    RAPIDJSON_ASSERT(str != 0);
    (void)copy;
    PrettyPrefix(kNumberType);
    return Base::EndValue(Base::WriteString(str, length));
  }

  bool String(const Ch *str, SizeType length, bool copy = false) {
    RAPIDJSON_ASSERT(str != 0);
    (void)copy;
    PrettyPrefix(kStringType);
    return Base::EndValue(Base::WriteString(str, length));
  }

#if RAPIDJSON_HAS_STDSTRING
  bool String(const std::basic_string<Ch> &str) {
    return String(str.data(), SizeType(str.size()));
  }
#endif

  bool StartObject() {
    PrettyPrefix(kObjectType);
    new (Base::level_stack_.template Push<typename Base::Level>())
        typename Base::Level(false);
    return Base::WriteStartObject();
  }

  bool Key(const Ch *str, SizeType length, bool copy = false) {
    return String(str, length, copy);
  }

#if RAPIDJSON_HAS_STDSTRING
  bool Key(const std::basic_string<Ch> &str) {
    return Key(str.data(), SizeType(str.size()));
  }
#endif

  bool EndObject(SizeType memberCount = 0) {
    (void)memberCount;
    RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >=
                     sizeof(typename Base::Level));  // not inside an Object
    RAPIDJSON_ASSERT(!Base::level_stack_.template Top<typename Base::Level>()
                          ->inArray);  // currently inside an Array, not Object
    RAPIDJSON_ASSERT(
        0 ==
        Base::level_stack_.template Top<typename Base::Level>()->valueCount %
            2);  // Object has a Key without a Value

    bool empty =
        Base::level_stack_.template Pop<typename Base::Level>(1)->valueCount ==
        0;

    if (!empty) {
      Base::os_->Put('\n');
      WriteIndent();
    }
    bool ret = Base::EndValue(Base::WriteEndObject());
    (void)ret;
    RAPIDJSON_ASSERT(ret == true);
    if (Base::level_stack_.Empty())  // end of json text
      Base::Flush();
    return true;
  }

  bool StartArray() {
    PrettyPrefix(kArrayType);
    new (Base::level_stack_.template Push<typename Base::Level>())
        typename Base::Level(true);
    return Base::WriteStartArray();
  }

  bool EndArray(SizeType memberCount = 0) {
    (void)memberCount;
    RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >=
                     sizeof(typename Base::Level));
    RAPIDJSON_ASSERT(
        Base::level_stack_.template Top<typename Base::Level>()->inArray);
    bool empty =
        Base::level_stack_.template Pop<typename Base::Level>(1)->valueCount ==
        0;

    if (!empty && !(formatOptions_ & kFormatSingleLineArray)) {
      Base::os_->Put('\n');
      WriteIndent();
    }
    bool ret = Base::EndValue(Base::WriteEndArray());
    (void)ret;
    RAPIDJSON_ASSERT(ret == true);
    if (Base::level_stack_.Empty())  // end of json text
      Base::Flush();
    return true;
  }

  //@}

  /*! @name Convenience extensions */
  //@{

  //! Simpler but slower overload.
  bool String(const Ch *str) { return String(str, internal::StrLen(str)); }
  bool Key(const Ch *str) { return Key(str, internal::StrLen(str)); }

  //@}

  //! Write a raw JSON value.
  /*!
      For user to write a stringified JSON as a value.

      \param json A well-formed JSON value. It should not contain null character
     within [0, length - 1] range. \param length Length of the json. \param type
     Type of the root of json. \note When using PrettyWriter::RawValue(), the
     result json may not be indented correctly.
  */
  bool RawValue(const Ch *json, size_t length, Type type) {
    RAPIDJSON_ASSERT(json != 0);
    PrettyPrefix(type);
    return Base::EndValue(Base::WriteRawValue(json, length));
  }

 protected:
  void PrettyPrefix(Type type) {
    (void)type;
    if (Base::level_stack_.GetSize() != 0) {  // this value is not at root
      typename Base::Level *level =
          Base::level_stack_.template Top<typename Base::Level>();

      if (level->inArray) {
        if (level->valueCount > 0) {
          Base::os_->Put(
              ',');  // add comma if it is not the first element in array
          if (formatOptions_ & kFormatSingleLineArray) Base::os_->Put(' ');
        }

        if (!(formatOptions_ & kFormatSingleLineArray)) {
          Base::os_->Put('\n');
          WriteIndent();
        }
      } else {  // in object
        if (level->valueCount > 0) {
          if (level->valueCount % 2 == 0) {
            Base::os_->Put(',');
            Base::os_->Put('\n');
          } else {
            Base::os_->Put(':');
            Base::os_->Put(' ');
          }
        } else
          Base::os_->Put('\n');

        if (level->valueCount % 2 == 0) WriteIndent();
      }
      if (!level->inArray && level->valueCount % 2 == 0)
        RAPIDJSON_ASSERT(type == kStringType);  // if it's in object, then even
                                                // number should be a name
      level->valueCount++;
    } else {
      RAPIDJSON_ASSERT(
          !Base::hasRoot_);  // Should only has one and only one root.
      Base::hasRoot_ = true;
    }
  }

  void WriteIndent() {
    size_t count =
        (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) *
        indentCharCount_;
    PutN(*Base::os_, static_cast<typename OutputStream::Ch>(indentChar_),
         count);
  }

  Ch indentChar_;
  unsigned indentCharCount_;
  PrettyFormatOptions formatOptions_;

 private:
  // Prohibit copy constructor & assignment operator.
  PrettyWriter(const PrettyWriter &);
  PrettyWriter &operator=(const PrettyWriter &);
};

RAPIDJSON_NAMESPACE_END

#if defined(__clang__)
RAPIDJSON_DIAG_POP
#endif

#ifdef __GNUC__
RAPIDJSON_DIAG_POP
#endif

#endif  // RAPIDJSON_RAPIDJSON_H_
